package api

import (
	"math"
	"math/big"
	"kumachan/stdlib"
	"kumachan/standalone/rx"
	"kumachan/standalone/qt"
	"kumachan/standalone/util"
	"kumachan/interpreter/runtime/lib/ui"
	"kumachan/interpreter/runtime/lib/ui/vdom"
	"kumachan/interpreter/runtime/lib/container"
	"kumachan/interpreter/runtime/rt"
)


func adaptAssetList(v rt.Value) ([] ui.WebAsset) {
	var l = container.ListFrom(v)
	var assets = make([] ui.WebAsset, l.Length())
	l.ForEach(func(i uint, v rt.Value) {
		assets[i] = ui.WebAssetFrom(v.(*stdlib.Asset))
	})
	return assets
}
func __UiGetAssetUrl(asset *stdlib.Asset) string {
	var key, del = qt.NewString(asset.Key())
	defer del()
	return qt.ConsumeString(qt.WebGetAssetURL(key))
}

func __UiInteractWithWindow(opts rt.TupleValue, h rt.InteropContext) rx.Observable {
	// TODO: Tuple2Struct(opts)
	var kind_ = opts.Elements[0].(string)
	var title = opts.Elements[1].(rx.Observable)
	var content = opts.Elements[2].(rx.Observable)
	var assets = adaptAssetList(opts.Elements[3])
	var pos_, set_pos = rt.Unwrap(opts.Elements[4].(rt.EnumValue))
	var size = opts.Elements[5].(rt.TupleValue)
	var on_close_, user_closable = rt.Unwrap(opts.Elements[6].(rt.EnumValue))
	var modal = rt.FromBool(opts.Elements[7].(rt.EnumValue))
	var debug = h.DebugOptions().DebugUI
	return rx.NewGoroutine(func(sender rx.Sender) {
		var sched = sender.Scheduler()
		var ctx = sender.Context()
		var recv = rx.Receiver { Context: ctx }
		var kind qt.WindowKind
		if (kind_ == "ordinary") { kind = qt.WindowKindOrdinary() } else
		if (kind_ == "dialog") { kind = qt.WindowKindDialog() } else
		if (kind_ == "popup") { kind = qt.WindowKindPopup() }
		var left, top int64
		if set_pos {
			var left_, top_ = rt.PairFromTuple2(pos_.(rt.TupleValue))
			left = util.GetInt64Integer(left_.(*big.Int))
			top = util.GetInt64Integer(top_.(*big.Int))
		}
		var width, height uint
		{
			var width_, height_ = rt.PairFromTuple2(size)
			width = util.GetUintNumber(width_.(*big.Int))
			height = util.GetUintNumber(height_.(*big.Int))
		}
		var opts = qt.WebWindowOptions {
			Kind:     kind,
			Title:    "",
			Icon:     nil,
			Width:    width,
			Height:   height,
			CloseBtn: user_closable,
			Modal:    modal,
			Debug:    debug,
		}
		var window qt.WebWindow
		var view qt.CustomWebView
		var dispose func()
		{ var wait = make(chan struct{})
		qt.CommitTask(func() {
			window, view, dispose = qt.WebWindowCreate(qt.Widget{}, opts)
			rx.Schedule(rx.Connect(title, rx.Callback(func(value_ rx.Object) rx.Observable {
				var value = value_.(string)
				return ui.NewQtTask(func() interface{} {
					window.SetPropString("windowTitle", value)
					return nil
				})
			})), sched, recv)
			wait <- struct{}{}
		})
		<- wait }
		var dispose_close_listener = func() {}
		var dispose_deactivate_listener = func() {}
		if user_closable {
			var on_close = on_close_.(rx.Observable)
			var trigger_close = func() {
				rx.Schedule(on_close, sched, recv)
			}
			dispose_close_listener =
				qt.Listen(window.Object, qt.EventClose(), true, func(_ qt.Event) {
					trigger_close()
				})
			if kind == qt.WindowKindPopup() {
				dispose_deactivate_listener =
					qt.Connect(window.Object, "deactivated()", func() {
						trigger_close()
					})
			}
		}
		var dispose_bind = ui.Bind(view, content, ui.BindOptions {
			Debug:  debug,
			Sched:  sched,
			Assets: assets,
		})
		{ var wait = make(chan struct{})
		qt.CommitTask(func() {
			window.Show(set_pos, int32(left), int32(top))
			wait <- struct{}{}
		})
		<- wait }
		ctx.WaitDispose(func() {
			dispose_bind()
			dispose_deactivate_listener()
			dispose_close_listener()
			dispose()
		})
	})
}

func __UiModalGetColor(title string, initial rt.TupleValue) rx.Observable {
	return ui.NewQtTask(func() interface{} {
		var h = util.GetUintNumber(initial.Elements[0].(*big.Int))
		var s = util.GetUintNumber(initial.Elements[1].(*big.Int))
		var l = util.GetUintNumber(initial.Elements[2].(*big.Int))
		var a = util.GetUintNumber(initial.Elements[3].(*big.Int))
		var initial = qt.Color { H: h, S: s, L: l, A: a }
		var selected, ok = qt.ColorDialog(qt.Widget{}, title, initial)
		if ok {
			var h = util.GetNumberUint(selected.H)
			var s = util.GetNumberUint(selected.S)
			var l = util.GetNumberUint(selected.L)
			var a = util.GetNumberUint(selected.A)
			return rt.Some(rt.Tuple(h, s, l, a))
		} else {
			return rt.None()
		}
	})
}
func __UiModalGetLine(title string, prompt string, initial string) rx.Observable {
	return ui.NewQtTask(func() interface{} {
		var text = initial
		var ok = qt.InputDialog(qt.Widget{}, title, prompt, false, &text)
		if ok {
			return rt.Some(text)
		} else {
			return rt.None()
		}
	})
}
func __UiModalGetSmallInteger(title string, prompt string, initial *big.Int, min *big.Int, max *big.Int, step *big.Int) rx.Observable {
	return ui.NewQtTask(func() interface{} {
		var clamp = func(n *big.Int) int32 {
			var int32min = big.NewInt(math.MinInt32)
			var int32max = big.NewInt(math.MaxInt32)
			if n.Cmp(min) < 0 { n = min }
			if n.Cmp(max) > 0 { n = max }
			if n.Cmp(int32min) < 0 { n = int32min }
			if n.Cmp(int32max) > 0 { n = int32max }
			return int32(n.Int64())
		}
		var value32 = clamp(initial)
		var min = clamp(min)
		var max = clamp(max)
		var step = clamp(step)
		var ok = qt.InputDialogInt32 (
			qt.Widget {},
			title, prompt, min, max, step, &value32,
		)
		if ok {
			var value = big.NewInt(int64(value32))
			return rt.Some(value)
		} else {
			return rt.None()
		}
	})
}
func __UiModalGetNormalFloat(title string, prompt string, initial float64, min float64, max float64, step float64, decimals *big.Int) rx.Observable {
	return ui.NewQtTask(func() interface{} {
		var clamp = func(x float64) float64 {
			if x < min { x = min }
			if x > max { x = max }
			return x
		}
		var value = clamp(initial)
		var step = clamp(step)
		var decimals = int32(decimals.Int64())
		var ok = qt.InputDialogFloat64 (
			qt.Widget {},
			title, prompt, min, max, step, decimals, &value,
		)
		if ok {
			return rt.Some(value)
		} else {
			return rt.None()
		}
	})
}

func __UiStaticComponent(thunk rt.Value, h rt.InteropContext) rx.Observable {
	return rx.NewPersistentThunk(func() rx.Object {
		return h.Call(thunk, nil)
	})
}

func __UiVdomNode(tag string) *vdom.Node {
	return &vdom.Node {
		Tag: tag,
		Props: vdom.Props {
			Styles: vdom.EmptyStyles(),
			Attrs:  vdom.EmptyAttrs(),
			Events: vdom.EmptyEvents(),
		},
		Content: vdom.EmptyContent(),
	}
}
func __UiVdomNodeWithStyles(node *vdom.Node, styles *vdom.Styles) *vdom.Node {
	return &vdom.Node {
		Tag:     node.Tag,
		Props:   vdom.Props {
			Attrs:  node.Attrs,
			Styles: ui.VdomMergeStyles(container.ListFrom([] *vdom.Styles {
				node.Styles, styles,
			})),
			Events: node.Events,
		},
		Content: node.Content,
	}
}
func __UiVdomNodeWithAttrs(node *vdom.Node, attrs *vdom.Attrs) *vdom.Node {
	return &vdom.Node {
		Tag:     node.Tag,
		Props:   vdom.Props {
			Attrs:  ui.VdomMergeAttrs(container.ListFrom([] *vdom.Attrs {
				node.Attrs, attrs,
			})),
			Styles: node.Styles,
			Events: node.Events,
		},
		Content: node.Content,
	}
}
func __UiVdomNodeWithEvents(node *vdom.Node, events *vdom.Events) *vdom.Node {
	return &vdom.Node {
		Tag:     node.Tag,
		Props:   vdom.Props {
			Styles: node.Styles,
			Attrs:  node.Attrs,
			Events: ui.VdomMergeEvents(container.ListFrom([] *vdom.Events {
				node.Events, events,
			})),
		},
		Content: node.Content,
	}
}
func __UiVdomNodeWithContent(node *vdom.Node, content vdom.Content) *vdom.Node {
	return &vdom.Node {
		Tag:     node.Tag,
		Props:   node.Props,
		Content: content,
	}
}

func __UiVdomStyles(styles container.Map) *vdom.Styles {
	if styles.IsEmpty() {
		return vdom.EmptyStyles()
	}
	return &vdom.Styles { Data: ui.VdomAdaptMap(styles) }
}
func __UiVdomAttrs(attrs container.Map) *vdom.Attrs {
	if attrs.IsEmpty() {
		return vdom.EmptyAttrs()
	}
	return &vdom.Attrs { Data: ui.VdomAdaptMap(attrs) }
}
func __UiVdomEventHandler(s rx.Sink, f rt.Value, h rt.InteropContext) rx.Observable {
	return rx.NewSync(func() (rx.Object, bool) {
		var adapter = func(obj rx.Object) rx.Object {
			var ev = obj.(*qt.WebEventPayload)
			return ev.Consume(func(ev *qt.WebEventPayload) interface{} {
				return h.Call(f, ev)
			})
		}
		var sink = rx.SinkAdapt(s, adapter)
		return &vdom.EventHandler { Handler: sink }, true
	})
}
func __UiVdomEvents(events container.Map) *vdom.Events {
	if events.IsEmpty() {
		return vdom.EmptyEvents()
	}
	return &vdom.Events { Data: ui.VdomAdaptMap(events) }
}
func __UiVdomText(text string) vdom.Content {
	var t = vdom.Text(text)
	return &t
}
func __UiVdomChildren(children_ rt.Value) vdom.Content {
	var list = container.ListFrom(children_)
	if list.Length() == 0 {
		return vdom.EmptyContent()
	}
	var children = make([] *vdom.Node, list.Length())
	list.ForEach(func(i uint, item rt.Value) {
		children[i] = item.(*vdom.Node)
	})
	var boxed = vdom.Children(children)
	return &boxed
}

func __UiEventPayloadGetBool(ev *qt.WebEventPayload, key string) rt.EnumValue {
	return rt.ToBool(ev.GetBool(key))
}
func __UiEventPayloadGetString(ev *qt.WebEventPayload, key string) string {
	return ev.GetString(key)
}
func __UiEventPayloadGetFloat(ev *qt.WebEventPayload, key string) float64 {
	var x = ev.GetFloat(key)
	if !(util.IsNormalFloat(x)) {
		panic("invalid float got from JS side")
	}
	return x
}
func __UiEventPayloadGetInteger(ev *qt.WebEventPayload, key string) *big.Int {
	var x = ev.GetFloat(key)
	if !(util.IsNormalFloat(x)) {
		panic("invalid float got from JS side")
	}
	var f = big.NewFloat(x)
	if f.IsInt() {
		var n, _ = f.Int(nil)
		return n
	}
	return big.NewInt(0)
}
func __UiEventPayloadGetNumber(ev *qt.WebEventPayload, key string) *big.Int {
	var x = ev.GetFloat(key)
	if !(util.IsNormalFloat(x)) {
		panic("invalid float got from JS side")
	}
	var f = big.NewFloat(x)
	if f.IsInt() {
		var n, _ = f.Int(nil)
		if n.Sign() >= 0 {
			return n
		}
	}
	return big.NewInt(0)
}


